# -*- coding: binary -*-
module Msf

module Exploit::Remote::VIMSoap
  include Msf::Exploit::Remote::HttpClient

  def vim_soap_envelope(body)
    soap_data = '<env:Envelope xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:env="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">'
    soap_data << '<env:Body>'
    soap_data << body
    soap_data <<  '</env:Body></env:Envelope>'
  end



  def vim_soap_propset(type,path,all = false)
    soap_data = '<propSet xsi:type="PropertySpec">'
    soap_data << '<type>' + type + '</type>'
    if all
      soap_data << '<all>true</all>'
    else
      soap_data << '<pathSet>' + path + '</pathSet>'
    end
    soap_data << '</propSet>'
  end



  def vim_soap_objset(type, ref)
    soap_data = '<objectSet>'
    soap_data << '<obj type="' + type + '">' + ref + '</obj>'
    soap_data << '</objectSet>'
  end



  def vim_soap_specset(path,type,ref,all=false)
    soap_data = '<specSet>'
    soap_data << vim_soap_propset(type,path,all)
    soap_data << vim_soap_objset(type,ref)
    soap_data << '</specSet>'
  end



  def vim_soap_retrieve_properties(path,type,ref,all=false)
    soap_data = '<RetrieveProperties xmlns="urn:vim25">'
    soap_data << '<_this type="PropertyCollector">' + @server_objects['propertyCollector'] + '</_this>'
    soap_data << vim_soap_specset(path,type,ref,all)
    soap_data << '</RetrieveProperties>'
  end



  def vim_soap_retrieve_service_content
    soap_data = '<RetrieveServiceContent xmlns="urn:vim25">'
    soap_data << '<_this type="ServiceInstance">ServiceInstance</_this>'
    soap_data << '</RetrieveServiceContent>'
  end



  def vim_soap_login(user,pass)
    soap_data = '<Login xmlns="urn:vim25">'
    soap_data << '<_this type="SessionManager">' + @server_objects['sessionManager'] + '</_this>'
    soap_data << '<userName>' + user + '</userName>'
    soap_data << '<password>' + pass + '</password>'
    soap_data << '</Login>'
  end



  def vim_soap_session_active?(key, user)
    soap_data = '<SessionIsActive xmlns="urn:vim25">'
    soap_data << '<_this type="SessionManager">' + @server_objects['sessionManager'] + '</_this>'
    soap_data << '<sessionID>' + key+ '</sessionID>'
    soap_data << '<userName>' + user + '</userName>'
    soap_data << '</SessionIsActive>'
  end




  def vim_soap_terminate_session(key)
    soap_data = '<TerminateSession xmlns="urn:vim25">'
    soap_data << '<_this xsi:type="ManagedObjectReference" type="SessionManager" >' + @server_objects['sessionManager'] + '</_this>'
    soap_data << '<sessionId>' + key + '</sessionId>'
    soap_data << '</TerminateSession>'
  end



  def vim_soap_retrieve_usergroups(domain=nil)
    soap_data = '<RetrieveUserGroups xmlns="urn:internalvim25">'
    soap_data << '<_this xsi:type="ManagedObjectReference" type="UserDirectory">' + @server_objects['userDirectory'] + '</_this>'
    soap_data << '<domain>' + domain + '</domain>' if domain
    soap_data << '<searchStr></searchStr><exactMatch>false</exactMatch><findUsers>true</findUsers><findGroups>true</findGroups>'
    soap_data << '</RetrieveUserGroups>'
  end



  def vim_soap_log_user_event_vm(vm_ref,msg)
    soap_data = '<LogUserEvent xmlns="urn:vim25">'
    soap_data << '<_this type="EventManager">' + @server_objects['eventManager'] + '</_this>'
    soap_data << '<entity type="VirtualMachine">' + vm_ref + '</entity>'
    soap_data << '<msg>' + msg + '</msg>'
    soap_data << '</LogUserEvent>'
  end



  def vim_soap_retrieve_all_permissions
    soap_data = '<RetrieveAllPermissions xmlns="urn:vim25">'
    soap_data << '<_this type="AuthorizationManager">' + @server_objects['authorizationManager'] + '</_this>'
    soap_data << '</RetrieveAllPermissions>'
  end



  def vim_soap_find_child_byname(type,entity,name)
    soap_data = '<FindChild xmlns="urn:vim25">'
    soap_data << '<_this type="SearchIndex">' + @server_objects['searchIndex'] + '</_this>'
    soap_data << '<entity type="' + type + '">' + entity + '</entity>'
    soap_data << '<name>' + name + '</name>'
    soap_data << '</FindChild>'
  end



  def vim_soap_power_on_vm(vm_ref)
    soap_data = '<PowerOnVM_Task xmlns="urn:vim25">'
    soap_data << '<_this type="VirtualMachine">' + vm_ref + '</_this>'
    soap_data << '</PowerOnVM_Task>'
  end



  def vim_soap_power_off_vm(vm_ref)
    soap_data = '<PowerOffVM_Task xmlns="urn:vim25">'
    soap_data << '<_this type="VirtualMachine">' + vm_ref + '</_this>'
    soap_data << '</PowerOffVM_Task>'
  end



  def vim_soap_create_screenshot(vm_ref)
    soap_data = '<CreateScreenshot_Task xmlns="urn:vim25">'
    soap_data << '<_this type="VirtualMachine">' + vm_ref + '</_this>'
    soap_data << '</CreateScreenshot_Task>'
  end



  def vim_send_soap_request(soap_data)
    res = send_request_cgi({
      'uri'     => '/sdk',
      'method'  => 'POST',
      'agent'   => 'VMware VI Client',
      'cookie'  => @vim_cookie,
      'data' => soap_data,
      'headers' => { 'SOAPAction' => @soap_action}
    }, 25)
    return :noresponse unless res
    if res.body.include? "NotAuthenticatedFault"
      return :expired
    elsif res.body.include? "<faultstring>"
      @vim_soap_error = res.body.match(/<faultstring>([^\c ]+?)<\/faultstring>/)[1]
      return :error
    elsif res.code != 200
      @vim_soap_error = "An unknown error was encountered"
      return :error
    else
      return Hash.from_xml(res.body)['Envelope']['Body']
    end
  end

  def vim_get_session
    soap_data = vim_soap_envelope(vim_soap_retrieve_service_content)
    res = send_request_cgi({
      'uri'     => '/sdk',
      'method'  => 'POST',
      'agent'   => 'VMware VI Client',
      'data' => soap_data,
      'headers' => { 'SOAPAction' => @soap_action}
    }, 25)
    return false unless res and res.code == 200
     @server_objects = (((Hash.from_xml(res.body)['Envelope'] || {})['Body'] || {})['RetrieveServiceContentResponse'] || {})['returnval']
     @soap_action = "urn:vim25/#{(@server_objects['about'] || {})['apiVersion']}"
    if res.headers['Set-Cookie']
      @vim_cookie = res.headers['Set-Cookie']
      return true
    else
      return false
    end
  end



  def vim_do_login(user, pass)
    unless vim_get_session
      return false
    end
    soap_data = vim_soap_envelope(vim_soap_login(user,pass))
    res = send_request_cgi({
        'uri'     => '/sdk',
        'method'  => 'POST',
        'agent'   => 'VMware VI Client',
        'cookie'  => @vim_cookie,
        'data' => soap_data,
        'headers' => { 'SOAPAction' => @soap_action}
        }, 25)
    if res.code == 200
      return :success
    else
      return :fail
    end
  end



  def vim_get_session_list
    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('sessionList','SessionManager', @server_objects['sessionManager']))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      session_list = []
      session_list << res['RetrievePropertiesResponse']['returnval']['propSet']['val']['UserSession']
      return session_list.flatten.compact
    else
      return res
    end
  end



  def vim_session_is_active(key, username)
    soap_data = vim_soap_envelope(vim_soap_session_active?(key,username))
    res = vim_send_soap_request(soap_data)
    print_status "Error: #{@vim_soap_error}"
    if res.class == Hash
      active = res['SessionIsActiveResponse']['returnval']
      return active
    else
      return res
    end
  end



  def vim_terminate_session(key)
    soap_data = vim_soap_envelope(vim_soap_terminate_session(key))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      return :success
    else
      return res
    end
  end



  def vim_get_domains
    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('domainList', 'UserDirectory', @server_objects['userDirectory']))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      domains = []
      domains << res['RetrievePropertiesResponse']['returnval']['propSet']['val']['string']
      return domains.flatten.compact
    else
      return res
    end
  end



  def vim_get_user_list(domain=nil)
    soap_data = vim_soap_envelope(vim_soap_retrieve_usergroups(domain))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      return nil unless res['RetrieveUserGroupsResponse']['returnval']
      user_list = []
      user_list <<  res['RetrieveUserGroupsResponse']['returnval']
      return user_list.flatten.compact
    else
      return res
    end
  end



  def vim_log_event_vm(vm_ref, msg)
    soap_data = vim_soap_envelope(vim_soap_log_user_event_vm(vm_ref,msg))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      return :success
    else
      return res
    end
  end



  def vim_get_all_permissions
    soap_data = vim_soap_envelope(vim_soap_retrieve_all_permissions)
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      permissions = []
      permissions << res['RetrieveAllPermissionsResponse']['returnval']
      return permissions.flatten.compact
    else
      return res
    end
  end



  def vim_get_roles
    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('roleList', 'AuthorizationManager', @server_objects['authorizationManager']))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      roles = []
      roles << res['RetrievePropertiesResponse']['returnval']['propSet']['val']['AuthorizationRole']
      return roles.flatten.compact
    else
      return res
    end
  end



  def vim_get_dc_name(dc)
    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('name','Datacenter',dc))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      return res['RetrievePropertiesResponse']['returnval']['propSet']['val']
    else
      return res
    end
  end


  def vim_get_dcs
    soap_data = vim_soap_envelope(vim_soap_retrieve_service_content)
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      @server_objects.merge!(res['RetrieveServiceContentResponse']['returnval'])
    else
      return res
    end

    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('content', 'ServiceInstance', 'ServiceInstance'))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      hash = res['RetrievePropertiesResponse']['returnval']['propSet']['val']
      hash.delete('xsi:type')
      @server_objects.merge!(hash)
    else
      return res
    end

    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('childEntity', 'Folder', @server_objects['rootFolder']))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      tmp_dcs = []
      tmp_dcs << res['RetrievePropertiesResponse']['returnval']['propSet']['val']['ManagedObjectReference']
      tmp_dcs.flatten!
      tmp_dcs.each{|dc| @dcs << { 'name' => vim_get_dc_name(dc) , 'ref' => dc}}
    else
      return res
    end
  end



  def vim_get_hosts(datacenter)
    dc_hosts = []
    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('hostFolder', 'Datacenter' , datacenter))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      host_folders = []
      host_folders << res['RetrievePropertiesResponse']['returnval']['propSet']['val']
      host_folders.flatten!
    else
      return res
    end

    compute_refs = []
    host_folders.each do |folder|
      soap_data = vim_soap_envelope(vim_soap_retrieve_properties('childEntity', 'Folder' , folder))
      res = vim_send_soap_request(soap_data)
      if res.class == Hash
        ref = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['ManagedObjectReference']
        unless ref.nil?
          compute_refs << ref
        end
      else
        return res
      end
    end
    compute_refs.flatten!

    compute_refs.each do |ref|
      next if ref.start_with? "group-"
      soap_data = vim_soap_envelope(vim_soap_retrieve_properties('host', 'ComputeResource' , ref))
      res = vim_send_soap_request(soap_data)
      if res.class == Hash
        dc_hosts << res['RetrievePropertiesResponse']['returnval']['propSet']['val']['ManagedObjectReference']
      else
        return res
      end
    end
    dc_hosts.flatten!
    return dc_hosts
  end



  def vim_get_all_hosts
    @dcs.each{|dc| @hosts << vim_get_hosts(dc['ref'])}
    @hosts.flatten!
  end



  def vim_get_host_hw(host)
    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('hardware', 'HostSystem' , host))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      return res['RetrievePropertiesResponse']['returnval']['propSet']['val']
    else
      return res
    end
  end

  def vim_get_all_host_summary(hw=false)
    vim_setup_references
    summaries = []
    @hosts.each do |host|
      details = {}
      details[host] = vim_get_host_summary(host)
      if details and hw
        details.merge!(vim_get_host_hw(host))
      end
      summaries << details
    end
    return summaries.flatten.compact
  end

  def vim_get_vm_datastore(vm)
    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('datastore', 'VirtualMachine' , vm))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      datastore_refs = []
      datastore_refs << res['RetrievePropertiesResponse']['returnval']['propSet']['val']['ManagedObjectReference']
      datastore_refs.flatten!
      datastore_refs.compact!
      datastores = []
    else
      return res
    end

    datastore_refs.each do |datastore_ref|
      soap_data = vim_soap_envelope(vim_soap_retrieve_properties('info', 'Datastore' , datastore_ref))
      res = vim_send_soap_request(soap_data)
      if res.class == Hash
        datastore_name = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['name']
        datastore = { 'name' => datastore_name, 'ref' => datastore_ref}
        datastores << datastore
      else
        return res
      end
    end
    return datastores

  end

  def vim_find_vm_by_name(name)
    vim_setup_references
    @dcs.each do |dc|
      soap_data = vim_soap_envelope(vim_soap_retrieve_properties('vmFolder', 'Datacenter' , dc['ref']))
      res = vim_send_soap_request(soap_data)
      if res.class == Hash
        vm_folders = []
        vm_folders << res['RetrievePropertiesResponse']['returnval']['propSet']['val']
        vm_folders.flatten!
        vm_folders.compact!
      else
        return res
      end


      vm_folders.each do |vm_folder|
        soap_data = vim_soap_envelope(vim_soap_find_child_byname('Folder', vm_folder, name))
        res = vim_send_soap_request(soap_data)
        if res.class == Hash
          vmref = res['FindChildResponse']['returnval']
          if vmref
            return vmref
          else
            next
          end
        else
          return res
        end
      end
    end
    return nil
  end



  def vim_powerON_vm(vm_ref)
    soap_data = vim_soap_envelope(vim_soap_power_on_vm(vm_ref))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      task_id = res['PowerOnVM_TaskResponse']['returnval']
    else
      return res
    end

    state= "running"
    while state == "running"
      soap_data = vim_soap_envelope(vim_soap_retrieve_properties('info', 'Task', task_id))
      res = vim_send_soap_request(soap_data)
      if res.class == Hash
        state = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['state']
        case state
        when 'running'
          select(nil, nil, nil, 5)
        when 'error'
          if res['RetrievePropertiesResponse']['returnval']['propSet']['val']['error']['fault']['existingState'] == 'poweredOn'
            return 'alreadyON'
          end
        end
      else
        return res
      end
    end
    return state
  end



  def vim_powerOFF_vm(vm_ref)
    soap_data = vim_soap_envelope(vim_soap_power_off_vm(vm_ref))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      task_id = res['PowerOffVM_TaskResponse']['returnval']
    else
      return res
    end

    state= "running"
    while state == "running"
      soap_data = vim_soap_envelope(vim_soap_retrieve_properties('info', 'Task', task_id))
      res = vim_send_soap_request(soap_data)
      if res.class == Hash
        state = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['state']
        case state
        when 'running'
          select(nil, nil, nil, 5)
        when 'error'
          if res['RetrievePropertiesResponse']['returnval']['propSet']['val']['error']['fault']['existingState'] == 'poweredOn'
            return 'alreadyON'
          end
        end
      else
        return res
      end
    end
    return state
  end



  def vim_take_screenshot(vm, user, pass)
    soap_data = vim_soap_envelope(vim_soap_create_screenshot(vm['ref']))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      task_id = res['CreateScreenshot_TaskResponse']['returnval']
    else
      return res
    end

    state= "running"
    while state == "running"
      soap_data = vim_soap_envelope(vim_soap_retrieve_properties('info', 'Task', task_id))
      res = vim_send_soap_request(soap_data)
      if res.class == Hash
        state = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['state']
        screenshot_file = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['result']
      else
        return res
      end
    end
    unless screenshot_file
      return :error
    end
    (ss_folder, ss_file) = screenshot_file.split('/').last(2)
    ss_folder = Rex::Text.uri_encode(ss_folder)
    ss_file =  Rex::Text.uri_encode(ss_file)
    ss_path = "#{ss_folder}/#{ss_file}"
    datastores = vim_get_vm_datastore(vm['ref'])
    user_pass = Rex::Text.encode_base64(user + ":" + pass)
    datastores.each do |datastore|
      ss_uri = "/folder/#{ss_path}?dcPath=#{vm['dc_name']}&dsName=#{datastore['name']}"
      res = send_request_cgi({
        'uri'     => ss_uri,
        'method'  => 'GET',
        'agent'   => 'VMware VI Client',
        'cookie'  => @vim_cookie,
        'headers' => { 'Authorization' => "Basic #{user_pass}"}
      }, 25)
      next unless res
      if res.code == 200
        return res.body
      elsif res.code == 404
        next
      end
    end
    return :error
  end



  def vim_get_host_summary(host)
    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('summary', 'HostSystem', host))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      hash = res['RetrievePropertiesResponse']['returnval']['propSet']['val']
      hash['runtime'].delete('healthSystemRuntime')
      hash.delete('xsi:type')
      hash.delete('host')
      return hash
    else
      return res
    end
  end



  def vim_get_vms
    vim_setup_references
    @vmrefs = []
    vmlist= []
    @dcs.each do |dc|
      dc_vm_refs = vim_get_dc_vms(dc['ref'])
      next if dc_vm_refs.nil? or dc_vm_refs.empty?
      dc_vm_refs.flatten!
      dc_vm_refs.compact!
      next if dc_vm_refs.nil? or dc_vm_refs.empty?
      print_status "#{datastore['RHOST']} - DataCenter: #{dc['name']} Found a Total of #{dc_vm_refs.length} VMs"
      print_status "#{datastore['RHOST']}  - DataCenter: #{dc['name']} Estimated Time: #{((dc_vm_refs.length * 7) /60)} Minutes"
      dc_vm_refs.each do |ref|
        print_status "#{datastore['RHOST']}  -  DataCenter: #{dc['name']} - Getting Data for VM: #{ref}..."
        details  = vim_get_vm_info(ref)
        if details
          details['ref'] = ref
          details['dc_ref'] = dc['ref']
          details['dc_name'] = dc['name']
          vmlist << details
        end
      end
    end
    return vmlist
  end



  def vim_get_dc_vms(datacenter)
    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('vmFolder', 'Datacenter', datacenter))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      vmfolder = res['RetrievePropertiesResponse']['returnval']['propSet']['val']
    else
      return res
    end

    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('childEntity', 'Folder', vmfolder))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      vm_index_array = res['RetrievePropertiesResponse']['returnval']['propSet']['val']['ManagedObjectReference']
      vm_index_array.delete_if{|ref| ref.start_with? "group"} unless vm_index_array.nil? or vm_index_array.empty? or vm_index_array.class != Array
      return vm_index_array
    else
      return res
    end
  end

  def vim_get_vm_info(vm_ref)
    vim_setup_references
    soap_data = vim_soap_envelope(vim_soap_retrieve_properties('summary', 'VirtualMachine', vm_ref))
    res = vim_send_soap_request(soap_data)
    if res.class == Hash
      hash =  res['RetrievePropertiesResponse']['returnval']['propSet']['val']
      vm = hash['config']
      vm['runtime'] = hash['runtime']
      vm['guest'] = hash['guest']
      vm['quickStats'] = hash['quickStats']
      return vm
    else
      return res
    end
  end

  def vim_logged_in?
    return true if @vim_cookie
    return false
  end

  def vim_instance_vars_set?
    return false if @server_objects.nil? or @server_objects.empty?
    return false if @host.nil? or @host.empty?
    return true
  end

  def vim_setup_references
    unless vim_instance_vars_set?
      @dcs = []
      @hosts = []
      vim_get_dcs
      vim_get_all_hosts
      @hosts.flatten!
      @hosts.compact!
    end
  end

end
end
